package org.nehc.algorithm.redissonutils;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.*;
import org.redisson.api.options.KeysScanOptions;

import java.time.Duration;
import java.util.*;

/**
 * Redisson 基础缓存对象操作工具类
 * 提供基本对象的增删改查操作
 *
 * @author system
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public class RedissonObjectUtils {

    private static final RedissonClient CLIENT = RedissonUtils.getClient();

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param cacheName 缓存名称
     * @param key       缓存的键值
     * @param value     缓存的值
     */
    public static <T> void setCacheObject(final String cacheName, final String key, final T value) {
        setCacheObject(cacheName, key, value, false);
    }

    /**
     * 缓存基本的对象，保留当前对象 TTL 有效期
     *
     * @param cacheName 缓存名称
     * @param key       缓存的键值
     * @param value     缓存的值
     * @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90)
     * @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案
     */
    public static <T> void setCacheObject(final String cacheName, final String key, final T value, final boolean isSaveTtl) {
        RBucket<T> bucket = CLIENT.getBucket(RedissonUtils.getKey(cacheName, key));
        if (isSaveTtl) {
            try {
                bucket.setAndKeepTTL(value);
            } catch (Exception e) {
                long timeToLive = bucket.remainTimeToLive();
                setCacheObject(cacheName, key, value, Duration.ofMillis(timeToLive));
            }
        } else {
            bucket.set(value);
        }
    }

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param cacheName 缓存名称
     * @param key       缓存的键值
     * @param value     缓存的值
     * @param duration  时间
     */
    public static <T> void setCacheObject(final String cacheName, final String key, final T value, final Duration duration) {
        RBatch batch = CLIENT.createBatch();
        RBucketAsync<T> bucket = batch.getBucket(RedissonUtils.getKey(cacheName, key));
        bucket.setAsync(value);
        bucket.expireAsync(duration);
        batch.execute();
    }

    /**
     * 缓存基本的对象 —— 已有则返回缓存失败，否则返回缓存成功
     *
     * @param cacheName 缓存名称
     * @param key       缓存的键值
     * @param value     缓存的值
     * @param duration  超时时间
     * @return java.lang.Boolean 缓存是否成功
     */
    public static <T> Boolean setObjIfAbsent(final String cacheName, final String key, final T value, final Duration duration) {
        RBucket<Object> bucket = CLIENT.getBucket(RedissonUtils.getKey(cacheName, key));
        return bucket.setIfAbsent(value, duration);
    }

    /**
     * 注册对象监听器
     * <p>
     * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
     *
     * @param cacheName 缓存名称
     * @param key       缓存的键值
     * @param listener  监听器配置
     */
    public static <T> void addObjectListener(final String cacheName, final String key, final ObjectListener listener) {
        RBucket<T> result = CLIENT.getBucket(RedissonUtils.getKey(cacheName, key));
        result.addListener(listener);
    }

    /**
     * 设置有效时间
     *
     * @param cacheName 缓存名称
     * @param key       Redis键
     * @param timeout   超时时间
     * @return true=设置成功；false=设置失败
     */
    public static boolean expire(final String cacheName, final String key, final long timeout) {
        return expire(cacheName, key, Duration.ofSeconds(timeout));
    }

    /**
     * 设置有效时间
     *
     * @param cacheName 缓存名称
     * @param key       Redis键
     * @param duration  超时时间
     * @return true=设置成功；false=设置失败
     */
    public static boolean expire(final String cacheName, final String key, final Duration duration) {
        RBucket rBucket = CLIENT.getBucket(RedissonUtils.getKey(cacheName, key));
        return rBucket.expire(duration);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param cacheName 缓存名称
     * @param key       缓存键值
     * @return 缓存键值对应的数据
     */
    public static <T> T getCacheObject(final String cacheName, final String key) {
        return RedissonUtils.getFromLocalCacheOrRedis(cacheName, key, redisKey -> {
            RBucket<T> rBucket = CLIENT.getBucket(RedissonUtils.getKey(cacheName, redisKey));
            return rBucket.get();
        });
    }

    /**
     * 模糊查询获得缓存的基本对象
     *
     * @param cacheName 缓存键值
     * @param isRight   是否右匹配，否则左匹配
     * @return java.util.List<T> 缓存键值对应的数据
     * @since 2023/12/15 10:34
     */
    public static <T> List<T> getCacheObjectRight(final String cacheName, Boolean isRight) {
        RKeys keys = CLIENT.getKeys();
        ArrayList<T> resultList = new ArrayList<>();
        Iterable<String> keysByPattern = isRight ?
                keys.getKeys(KeysScanOptions.defaults().pattern(cacheName + "*"))
                : keys.getKeys(KeysScanOptions.defaults().pattern("*" + cacheName));
        for (String cacheKey : keysByPattern) {
            RBucket<T> rBucket = CLIENT.getBucket(cacheKey);
            resultList.add(rBucket.get());
        }
        return resultList;
    }

    /**
     * 获得key剩余存活时间
     *
     * @param cacheName 缓存名称
     * @param key       缓存键值
     * @return 剩余存活时间
     */
    public static <T> long getTimeToLive(final String cacheName, final String key) {
        RBucket<T> rBucket = CLIENT.getBucket(RedissonUtils.getKey(cacheName, key));
        return rBucket.remainTimeToLive();
    }

    /**
     * 删除单个对象
     *
     * @param cacheName 缓存名称
     * @param key       缓存的键值
     */
    public static boolean deleteObject(final String cacheName, final String key) {
        // 从本地缓存中删除
        RedissonLocalCacheUtils.invalidateLocalCache(cacheName, key);

        // 从Redis中删除
        return CLIENT.getBucket(RedissonUtils.getKey(cacheName, key)).delete();
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     */
    public static void deleteObject(final Collection collection) {
        RBatch batch = CLIENT.createBatch();
        collection.forEach(t -> {
            batch.getBucket(t.toString()).deleteAsync();
        });
        batch.execute();
    }

    /**
     * 检查缓存对象是否存在
     *
     * @param cacheName 缓存名称
     * @param key       缓存的键值
     */
    public static boolean isExistsObject(final String cacheName, final String key) {
        return CLIENT.getBucket(RedissonUtils.getKey(cacheName, key)).isExists();
    }

    /**
     * 批量获取缓存对象
     *
     * @param cacheName 缓存名称
     * @param keys      键列表
     * @return 键值对映射
     */
    public static <T> Map<String, T> getCacheObjects(final String cacheName, final List<String> keys) {
        if (keys == null || keys.isEmpty()) {
            return new HashMap<>();
        }

        Map<String, T> result = new HashMap<>();
        RBatch batch = CLIENT.createBatch();
        Map<String, RBucketAsync<T>> bucketMap = new HashMap<>();

        for (String key : keys) {
            String redisKey = RedissonUtils.getKey(cacheName, key);
            RBucketAsync<T> bucket = batch.getBucket(redisKey);
            bucketMap.put(key, bucket);
            bucket.getAsync();
        }

        BatchResult<?> batchResult = batch.execute();
        List<?> responses = batchResult.getResponses();

        int index = 0;
        for (String key : keys) {
            if (index < responses.size()) {
                T value = (T) responses.get(index);
                if (value != null) {
                    result.put(key, value);
                }
            }
            index++;
        }

        return result;
    }

    /**
     * 批量设置缓存对象
     *
     * @param cacheName   缓存名称
     * @param keyValueMap 键值对映射
     * @param duration    过期时间
     */
    public static <T> void setCacheObjects(final String cacheName, final Map<String, T> keyValueMap, final Duration duration) {
        if (keyValueMap == null || keyValueMap.isEmpty()) {
            return;
        }

        RBatch batch = CLIENT.createBatch();
        for (Map.Entry<String, T> entry : keyValueMap.entrySet()) {
            String redisKey = RedissonUtils.getKey(cacheName, entry.getKey());
            RBucketAsync<T> bucket = batch.getBucket(redisKey);
            bucket.setAsync(entry.getValue());
            if (duration != null) {
                bucket.expireAsync(duration);
            }
        }
        batch.execute();
    }

    /**
     * 批量设置缓存对象
     *
     * @param cacheName   缓存名称
     * @param keyValueMap 键值对映射
     */
    public static <T> void setCacheObjects(final String cacheName, final Map<String, T> keyValueMap) {
        setCacheObjects(cacheName, keyValueMap, null);
    }
}